package eventfax.db

import scala.dbc._
import scala.dbc.Syntax._
import scala.dbc.syntax.Statement._
import java.net.URI

trait ActiveRecord {
  val connection: Mysql = null
  def connect(host: String,port: Int,db: String,user: String,password: String) {
    this.connection = new Mysql("jdbc:mysql://" + host + ":" + port + "/" + db,user,password)
  }

  def connect(conn_map: java.util.Map) {
    var host: String = conn_map.get("host").asInstanceOf[String]
    var database: String = conn_map.get("database").asInstanceOf[String]
    var conn_str: String = host + database
    var username: String = conn_map.get("username").asInstanceOf[String]
    var password: String = conn_map.get("password").asInstanceOf[String]
    this.connnection = new Mysql(conn_str,username,password)
  }
}

class MySql(val uri_str: String,val user: String,val pass: String) extends Vendor {
  val uri = new URI(uri_str)
  val retainedConnections = 5
  val nativeDriverClass = Class.forName("com.mysql.jdbc.Driver")
  val urlProtocolString = "jdbc:mysql:"
}

object RichSQL {
  private def strm[X](f: RichResultSet => X, rs: ResultSet): Stream[X] = 
    if (rs.next) Stream.cons(f(new RichResultSet(rs)), strm(f, rs))
    else { rs.close(); Stream.empty };
  
  implicit def query[X](s: String, f: RichResultSet => X)(implicit stat: Statement) = {
    strm(f,stat.executeQuery(s));
  }
  
  implicit def conn2Statement(conn: Connection): Statement = conn.createStatement;
  
  implicit def rrs2Boolean(rs: RichResultSet) = rs.nextBoolean;
  implicit def rrs2Byte(rs: RichResultSet) = rs.nextByte;
  implicit def rrs2Int(rs: RichResultSet) = rs.nextInt;
  implicit def rrs2Long(rs: RichResultSet) = rs.nextLong;
  implicit def rrs2Float(rs: RichResultSet) = rs.nextFloat;
  implicit def rrs2Double(rs: RichResultSet) = rs.nextDouble;
  implicit def rrs2String(rs: RichResultSet) = rs.nextString;
  implicit def rrs2Date(rs: RichResultSet) = rs.nextDate;
  
  implicit def resultSet2Rich(rs: ResultSet) = new RichResultSet(rs);
  implicit def rich2ResultSet(r: RichResultSet) = r.rs;

  class RichResultSet(val rs: ResultSet) {
    var pos = 1
    def apply(i: Int) = { pos = i; this }
    
    def nextBoolean: Boolean = { val ret = rs.getBoolean(pos); pos = pos + 1; ret }
    def nextByte: Byte = { val ret = rs.getByte(pos); pos = pos + 1; ret }
    def nextInt: Int = { val ret = rs.getInt(pos); pos = pos + 1; ret }
    def nextLong: Long = { val ret = rs.getLong(pos); pos = pos + 1; ret }
    def nextFloat: Float = { val ret = rs.getFloat(pos); pos = pos + 1; ret }
    def nextDouble: Double = { val ret = rs.getDouble(pos); pos = pos + 1; ret }
    def nextString: String = { val ret = rs.getString(pos); pos = pos + 1; ret }
    def nextDate: Date = { val ret = rs.getDate(pos); pos = pos + 1; ret }
    
    def foldLeft[X](init: X)(f: (ResultSet, X) => X): X = rs.next match {
      case false => init
      case true => foldLeft(f(rs, init))(f)
    }
    def map[X](f: ResultSet => X) = {
      var ret = List[X]()
      while (rs.next())
      ret = f(rs) :: ret
      ret;
    }
  }
  
  implicit def str2RichPrepared(s: String)(implicit conn: Connection): RichPreparedStatement = conn prepareStatement(s);
  
  implicit def ps2Rich(ps: PreparedStatement) = new RichPreparedStatement(ps);
  implicit def rich2PS(r: RichPreparedStatement) = r.ps;
  
  class RichPreparedStatement(val ps: PreparedStatement) {
    var pos = 1;
    private def inc = { pos = pos + 1; this }
    
    def execute[X](f: RichResultSet => X): Stream[X] = {
      pos = 1; strm(f, ps.executeQuery)
    }
    def <<![X](f: RichResultSet => X): Stream[X] = execute(f);
    
    def execute = { pos = 1; ps.execute }
    def <<! = execute;
    
    def <<(b: Boolean) = { ps.setBoolean(pos, b); inc }
    def <<(x: Byte) = { ps.setByte(pos, x); inc }
    def <<(i: Int) = { ps.setInt(pos, i); inc }
    def <<(x: Long) = { ps.setLong(pos, x); inc }
    def <<(f: Float) = { ps.setFloat(pos, f); inc }
    def <<(d: Double) = { ps.setDouble(pos, d); inc }      
    def <<(o: String) = { ps.setString(pos, o); inc }
    def <<(x: Date) = { ps.setDate(pos, x); inc }
  }
  
  implicit def conn2Rich(conn: Connection) = new RichConnection(conn);
  
  class RichConnection(val conn: Connection) {
    def <<(sql: String) = new RichStatement(conn.createStatement) << sql;
    def <<(sql: Seq[String]) = new RichStatement(conn.createStatement) << sql;
  }
  
  implicit def st2Rich(s: Statement) = new RichStatement(s);
  implicit def rich2St(rs: RichStatement) = rs.s;
  
  class RichStatement(val s: Statement) {
    def <<(sql: String) = { s.execute(sql); this }
    def <<(sql: Seq[String]) = { for (val x <- sql) s.execute(x); this }
  }
}
